/* 
 * File:   OperacionCargaNuevoArchivo.cpp
 * Author: max
 * 
 * Created on May 31, 2013, 12:53 AM
 */

#include <sstream>

#include "client_OperacionCargaArchivo.h"
#include "common_FileUtils.h"
#include "common_NumeroUtils.h"
#include "common_Codec.h"

using namespace TP;
using namespace TP::Operaciones;
using namespace TP::Archivos;

#define OCA_ARCHIVO_INEXISTENTE 1
#define OCA_ESTADO_ARCHIVO_INVALIDO 2

OperacionCargaArchivo::OperacionCargaArchivo(uint32_t pToken, FileServiceProxy* pFileService, Repositorio* pRepositorio, const string &pNombreArchivo) : Operacion(pToken) {
    _fileService = pFileService;
    _repositorio = pRepositorio;
    _nombreArchivo = pNombreArchivo;
}

OperacionCargaArchivo::~OperacionCargaArchivo() {
    _fileService = NULL;
    _repositorio = NULL;
}

void OperacionCargaArchivo::ejecutar(){

    try
    {
        // Inicialmente se declara el archivo en el servidor.
        
        RepoItem* item = _repositorio->GetRepoItemByFilename(_nombreArchivo);
        
        if (NULL == item) throw OCA_ARCHIVO_INEXISTENTE;
        if (item->GetEstado() != ARCHIVO_A_PUBLICAR) throw OCA_ESTADO_ARCHIVO_INVALIDO;
        
        _fileService->DeclareFile(getId(), _nombreArchivo, item->GetVersion());
    }
    catch (const uint32_t &ex)
    {
        switch (ex)
        {
            case OCA_ARCHIVO_INEXISTENTE:
            {
                cerr << "Se intenta cargar un archivo en el servidor que no figura en el repositorio local." << endl;
                break;
            }
            case OCA_ESTADO_ARCHIVO_INVALIDO:
            {
                cerr << "Se intenta cargar un archivo cuyo estado no es ARCHIVO_A_PUBLICAR" << endl;
                break;
            }
        }
        this->setFinalizada(true);
    }
}

void OperacionCargaArchivo::procesarRespuesta (vector<string> &pRespuesta){

    /* Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     */
    
    if (FileServiceConstants::DECLARAR_ARCHIVO == pRespuesta[2])
    {
        this->manejarRespuestaDeclaracionArchivo(pRespuesta);
    }
    else if (FileServiceConstants::PUT_CHUNK == pRespuesta[2])
    {
        this->manejarRespuestaPutChunkArchivo(pRespuesta);
    }
    else if (FileServiceConstants::DEFINIR_ARCHIVO == pRespuesta[2])
    {
        this->manejarRespuestaDefinicionArchivo(pRespuesta);
    }
}

void OperacionCargaArchivo::manejarRespuestaDeclaracionArchivo (vector<string> &pRespuesta){

    /* Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * <resultado> = OK    => <args> = <nombre_archivo>
     * <resultado> = ERROR => <args> = <codigo_error>;<nombre_archivo>
     * 
     */
    
    if (FileServiceConstants::OK == pRespuesta[3])
    {
        this->putChunk(Codec::decodificar(pRespuesta[4]), 0);
    }
    else if (FileServiceConstants::ERROR == pRespuesta[3])
    {
        if (FileServiceConstants::ERROR_ARCHIVO_EXISTENTE == pRespuesta[4])
        {
            this->manejarErrorCargaInicialArchivoPreexistente (pRespuesta);
        }
        else
        {
            // Logueo el error.
            cerr << "Error de declaracion de archivo en el servidor.  Archivo existente " << pRespuesta[5] << endl;
            
            // Finalizo la operacion.
            this->setFinalizada(true);
        }
    }
}

void OperacionCargaArchivo::manejarRespuestaPutChunkArchivo (vector<string> &pRespuesta){

    /* Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * OK => <args> = <nombre_archivo>;<numero_chunk>
     * ERROR => <args> = <mensaje_error>;<nombre_archivo>;<numero_chunk>
     */

    if (FileServiceConstants::OK == pRespuesta[3])
    {
        
        uint32_t numeroChunk = NumeroUtils::ParseUInt32(pRespuesta[5]) + 1;
        string nombreArchivo = Codec::decodificar(pRespuesta[4]);
        
        if (numeroChunk < _repositorio->GetRepoItemByFilename(nombreArchivo)->GetCantidadHashes())
        {
            this->putChunk(nombreArchivo, numeroChunk);
        }
        else
        {
            _fileService->DefineFile(this->getId(), nombreArchivo);
        }
    }
    else
    {
        cerr << pRespuesta[4] << endl;
        this->setFinalizada(true);
    }
}

void OperacionCargaArchivo::manejarRespuestaDefinicionArchivo (vector<string> &pRespuesta){

    /* Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * OK => <args> = <nombre_archivo>
     * ERROR => <args> = <mensaje_error>;<nombre_archivo>
     * 
     */          
    
    // El nombre de archivo viene codificado a fin de soportar caracteres especiales.
    string nombreArchivo = Codec::decodificar(pRespuesta[4]);
    
    if (FileServiceConstants::OK == pRespuesta[3])
    {
        // Se sincronizan los repoitems del server y el local.

        RepoItem* itemLocal = _repositorio->GetRepoItemByFilename(nombreArchivo);
        
        itemLocal->SetEstado(ARCHIVO_CARGADO);
        
        cerr << "Archivo " + nombreArchivo + " cargado correctamente en el servidor." << endl;
    }
    else
    {
        cerr << "Fallo al cargar el archivo "  + pRespuesta[5] + ". " << nombreArchivo << endl;
    }
    
    this->setFinalizada(true);
}

void OperacionCargaArchivo::putChunk (const string &pNombreArchivo, int32_t pNumeroChunk) {
    
    // Comiteo el chunk.
    
    string rutaArchivo = _repositorio->GetRepoItemByFilename(pNombreArchivo)->GetRutaCompletaArchivo();
    
    FileUtils fileUtils;
    
    // Computo el chunk.
    uint8_t* chunk = NULL;
    uint32_t chunkSize = 0;

    fileUtils.ComputarChunk(chunk, chunkSize, rutaArchivo, (uint32_t) pNumeroChunk);

    string hash = fileUtils.GetHash(rutaArchivo, (uint32_t) pNumeroChunk);

    _fileService->PutFileChunk(getId(), pNombreArchivo, (uint32_t) pNumeroChunk, hash, chunk, chunkSize);
    
}

void OperacionCargaArchivo::manejarErrorCargaInicialArchivoPreexistente (vector<string> &pRespuesta){

    /* Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * <resultado> = ERROR => <args> = <codigo_error>;<nombre_archivo>
     * 
     */    
    
    // Configuro el estado del archivo a Archivo en conflicto.

    RepoItem* item = _repositorio->GetRepoItemByFilename(Codec::decodificar(pRespuesta[5]));
    item->SetEstado(ARCHIVO_EN_CONFLICTO);

    // Registro el error en el log.
    
    cerr << "Fallo al publicar el archivo en el servidor.  "
            "Existe un archivo con el mismo nombre  " << Codec::decodificar(pRespuesta[4]) << ".  "
            "Se configura el estado del archivo en conflicto para su posterior resolucion."
             << endl;
    
    // Finalizo la operacion.
    
    this->setFinalizada(true);
}